-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

-- Description: sdr lifecycle management of every host.
local log = require 'mc.logging'
local mc_utils = require 'mc.utils'
local core = require 'dbus.core'
local utils = require 'sensor_utils'
local err = require 'sensor_error'
local cc = require 'sdr.sdr_const'
local common = require 'sdr.sdr_common'

local shm = core.SharedMemory

local sdr_group = {}
sdr_group.__index = sdr_group

function sdr_group.new(host_id)
    return setmetatable({
        host_id = host_id,
        sdr_count = 0,
        repos = {},
        sdr_index = {}
    }, sdr_group)
end

function sdr_group:sdr_clear_all()
    self.sdr_count = 0
    self.repos = {}
    local ok, ret = pcall(utils.shm_lock_exec, shm, function ()
        local sdr_datas = shm.get_sdr_datas()
        sdr_datas:clear_static_records()
    end)
    if not ok then
        log:info('clear sdr static record in shm failed, ret=%s', ret)
    end
    self.sdr_index = {}
end

function sdr_group:get_sdr_record(recid)
    return self.repos[recid].sdr_record
end

function sdr_group:update_sdr_index(index)
    for id, idx in pairs(self.sdr_index) do
        if idx > index then
            self.sdr_index[id] = idx - 1
        end
    end
end

function sdr_group:delete_sdr(id, index)
    -- 删除对应的 SDR 记录，并更新对应关系
    for i = index, self.sdr_count - 1 do
        self.repos[i] = self.repos[i + 1]
    end
    self.repos[self.sdr_count] = nil
    local ok, ret = pcall(utils.shm_lock_exec, shm, function ()
        local sdr_datas = shm.get_sdr_datas()
        sdr_datas:del_static_record(self.host_id, index)
    end)
    if not ok then
        log:info('delete sdr static record in shm failed, ret=%s', ret)
    end

    self.sdr_index[id] = nil
    self:update_sdr_index(index)
    self.sdr_count = self.sdr_count - 1
end

function sdr_group:add_sdr_to_repo(record, header)
    self.sdr_count = self.sdr_count + 1
    self.repos[self.sdr_count] = {
        sdr_record = record,
        sdr_header = header
    }
    local ok, ret = pcall(utils.shm_lock_exec, shm, function ()
        local sdr_datas = shm.get_sdr_datas()
        sdr_datas:add_static_record(self.host_id, record)
    end)
    if not ok then
        log:info('add sdr static record to shm failed, ret=%s', ret)
    end
end


local register_handler = {
    ThresholdSensor = function (self, obj, id)
        local header, body = common.generate_threshold(obj, self.sdr_count + 1)
        self:add_sdr_to_repo(body, header)
        utils.push_regist_dump('threshold sdr %s of host %s is registered.', id, self.host_id)
    end,
    DiscreteSensor = function (self, obj, id)
        local header, body = common.generate_discrete(obj, self.sdr_count + 1)
        self:add_sdr_to_repo(body, header)
        utils.push_regist_dump('discrete sdr %s of host %s is registered.', id, self.host_id)
    end,
    MCDLSDR = function (self, obj, id)
        local header, body = common.generate_mcdl(obj, self.sdr_count + 1)
        self:add_sdr_to_repo(body, header)
        utils.push_regist_dump('mcdl sdr %s of host %s is registered.', id, self.host_id)
    end,
    DEASDR = function (self, obj, id)
        local header, body = common.generate_dea(obj, self.sdr_count + 1)
        self:add_sdr_to_repo(body, header)
        utils.push_regist_dump('dea sdr %s of host %s is registered.', id, self.host_id)
    end
}
function sdr_group:register(clz, obj)
    local id = obj:get_object_name()

    if not register_handler[clz] then
        log:notice('current sensor class %s cannot be supported to register.', clz)
        return false
    end

    -- 生成 SDR 并且记录对应的 sensor 记录的 SDR 索引
    register_handler[clz](self, obj, id)
    self.sdr_index[id] = self.sdr_count
    return true
end

function sdr_group:unregister(obj)
    local id = obj:get_object_name()
    local index = self.sdr_index[id]
    if not index or index > self.sdr_count then
        log:info('current sensor [%s] of host %s has no SDR and no need to unregister.', id, self.host_id)
        return false
    end

    self:delete_sdr(id, index)
    utils.push_regist_dump('sdr %s of host %s is unregistered.', id, self.host_id)
    log:notice('sdr of sensor [%s] of host %s has been unregistered', id, self.host_id)
    return true
end

function sdr_group:update_sdr_to_repo(index, record, header)
    self.repos[index] = {
        sdr_record = record,
        sdr_header = header
    }
    local ok, ret = pcall(utils.shm_lock_exec, shm, function ()
        local sdr_datas = shm.get_sdr_datas()
        sdr_datas:update_static_record(self.host_id, index, record)
    end)
    if not ok then
        log:info('update sdr static record in shm failed, ret=%s', ret)
    end
end


local update_handler = {
    ThresholdSensor = function (self, obj, index)
        local header, body = common.generate_threshold(obj, index)
        self:update_sdr_to_repo(index, body, header)
    end,
    DiscreteSensor = function (self, obj, index)
        local header, body = common.generate_discrete(obj, index)
        self:update_sdr_to_repo(index, body, header)
    end,
    MCDLSDR = function (self, obj, index)
        local header, body = common.generate_mcdl(obj, index)
        self:update_sdr_to_repo(index, body, header)
    end,
    DEASDR = function (self, obj, index)
        local header, body = common.generate_dea(obj, index)
        self:update_sdr_to_repo(index, body, header)
    end
}
function sdr_group:update_sdr(clz, obj)
    local id =  obj:get_object_name()
    local index = self.sdr_index[id]
    if not index or not self.repos[index] then
        log:error('sensor %s of host %s has no sdr record and cannot be updated.', id, self.host_id)
        return
    end

    if not update_handler[clz] then
        log:notice('current sensor class %s cannot be supported to register.', clz)
        return
    end
    update_handler[clz](self, obj, index)
end

function sdr_group:register_fru(mode, id, name, path)
    local duplicated_idx
    if self.sdr_index[path] then
        if mode == utils.FRU_SDR_BY_OBJ then
            return false
        end
        duplicated_idx = self.sdr_index[path]
    end

    local record_id = duplicated_idx or (self.sdr_count + 1)
    local sdr_header, sdr_record = common.generate_fru(id, name, record_id)
    local desc = mode == utils.FRU_SDR_BY_OBJ and 'obj' or 'id'
    if not duplicated_idx then
        self:add_sdr_to_repo(sdr_record, sdr_header)
        self.sdr_index[path] = self.sdr_count
        utils.push_regist_dump('fru sdr %s(%d) of host %s is registered by %s.',
        name, id, self.host_id, desc)
    else
        self:update_sdr_to_repo(duplicated_idx, sdr_record, sdr_header)
        utils.push_regist_dump('fru sdr %s(%d) of host %s is replaced by %s.',
            name, id, self.host_id, desc)
    end

    return true
end

function sdr_group:unregister_fru(id, name, path)
    local index = self.sdr_index[path]
    if not index or index > self.sdr_count then
        log:info('current fru [%s] has no SDR and no need to unregister.', name)
        return false
    end

    self:delete_sdr(path, index)
    utils.push_regist_dump('fru sdr %s(%d) of host %s is unregistered.', name, id, self.host_id)
    return true
end

function sdr_group:add_sdr(datas)
    local sdr_header = common.generate_sdr_header()
    self:add_sdr_to_repo(datas, sdr_header)
    self.add_timestamp = os.time()
    return err.SUCCESS
end

function sdr_group:add_partial_sdr(offset, progress, datas)
    if offset + #datas > cc.SDR_MAX_LEN then
        return err.ERR_OUT_OF_RANGE
    end

    -- 由于会多次调用该接口保存完整的SDR数据，因此这地方sdr_record需要成员变量来保障数据接收
    if progress == cc.SDR_PARTIAL_ADD_IN_PROGRESS then
        self.sdr_record = self.sdr_record and self.sdr_record .. datas or datas
        return err.SUCCESS
    elseif progress == cc.SDR_PARTIAL_ADD_FINISHED then
        self.sdr_record = self.sdr_record and self.sdr_record .. datas or datas
        if offset + #datas ~= (5 + string.byte(self.sdr_record, 5)) then
            return err.ERR_CANNOT_RESPONSE
        end

        self:add_sdr(self.sdr_record)
        self.sdr_record = nil
        return err.SUCCESS
    end
    return err.ERR_INVALID_FIELD
end

return sdr_group